import { merge } from 'lodash-es';
import type { $Fetch } from 'nitropack';
import type { FetchError } from 'ofetch';
import type { AsyncData, AsyncDataOptions, NuxtError } from '#app';
import { reTokenStorage, useUserStore } from '~/composables/store/user-store';
import type { KeysOf, PickFrom } from '#app/composables/asyncData';
import type { Result } from '~/utils/api/type';
import type { RefreshToken } from '~/utils/api/user-api';

// 客户端发送请求的地址
const CLIENT_BASE_URL = 'http://localhost:8085';

// 服务端发送请求的地址
const SERVER_BASE_URL = 'http://localhost:8085';

type FetchType = $Fetch;
type FetchOptions = Parameters<FetchType>[1];

const fetcher = $fetch.create({
	baseURL: CLIENT_BASE_URL,
	method: 'get',
	onResponseError: async ({ request, response }) => {
		if (response.status === 401 && reTokenStorage.value) {
			try {
				const tokenResult = await $fetch<Result<RefreshToken>>(`/api/v1/auth/refreshToken`, {
					baseURL: CLIENT_BASE_URL,
					method: 'post',
					body: {
						refreshToken: reTokenStorage.value,
					},
				});
				if (tokenResult.code === 200) {
					return Promise.reject({ type: 'retry', response, request, token: tokenResult.data });
				}
			}
			catch (error) {
				const err = error as FetchError<Result<RefreshToken>>;
				if (err.data?.code === 11001) {
					useUserStore().logout();
				}
			}
		}
		return Promise.reject({ response });
	},
});

/**
 * 使用客户端发送请求
 *
 * ```ts
 * const result = await useClientRequest<Result<Article>>()
 * ```
 *
 * @async 使用 async/await 或者 then/catch 处理返回值
 * @param url 请求地址
 * @param body 请求体
 * @param query 请求参数
 * @param params 请求参数
 * @param options 请求配置
 * @return {Promise} Promise包装数据类型T
 */
export async function useClientRequest<T>({
	url,
	body,
	query,
	params,
	options,
}: {
	url: string;
	body?: object;
	query?: object;
	params?: object;
	options?: FetchOptions;
}): Promise<T> {
	const userStore = useUserStore();
	const acToken = userStore.user?.accessToken;
	const Authorization = acToken ? `Bearer ${acToken}` : '';
	const defaultOptions: FetchOptions = {
		headers: { Authorization } as HeadersInit,
		body,
		query,
		params,
	};

	return new Promise<T>((resolve, reject) => {
		fetcher<T>(url, merge(defaultOptions, options))
			.then((value) => {
				resolve(value);
			})
			.catch((reason) => {
				if (reason.type === 'retry' && reason.token) {
					userStore.setUser({
						accessToken: reason.token.accessToken,
						refreshToken: reason.token.refreshToken,
					});
					reTokenStorage.value = reason.token.refreshToken;
					// 使用reason中的数据重新发送请求
					resolve(
						$fetch<T>(reason.request,
							merge(defaultOptions,
								options,
								{ headers: { Authorization: `Bearer ${reason.token.accessToken}` } }),
						));
				}
				reject(reason.response);
			});
	});
}

/**
 * 使用服务端发送请求
 *
 * ```ts
 * const { data, pending, error, refresh } = useServerRequest<Result<Article>>()
 * ```
 *
 * @param url 请求地址
 * @param body 请求体
 * @param query 请求参数
 * @param params 请求参数
 * @param options 请求配置
 * @returns {AsyncData.data} data: 返回的结果
 * @returns {AsyncData.pending} pending: 一个布尔值，指示数据是否仍在获取中
 * @returns {AsyncData.refresh} refresh/execute: 一个可以用来刷新handler函数返回的数据的函数
 * @returns {AsyncData.error} error: 如果数据获取失败，则为一个错误对象
 * @returns {AsyncData.status} status: 一个字符串，表示数据请求的状态("idle"、"pending"、"success"、"error")
 */
export function useServerRequest<T>({
	url,
	body,
	query,
	params,
	options,
}: {
	url: string;
	body?: object;
	query?: object;
	params?: object;
	options?: FetchOptions & AsyncDataOptions<T>;
}): AsyncData<PickFrom<T, KeysOf<T>> | null, NuxtError | null> {
	const defaultOptions: FetchOptions = {
		baseURL: SERVER_BASE_URL,
		method: 'get',
		body,
		query,
		params,
	};
	return useAsyncData<T>(
		url,
		() => $fetch<T>(url, merge(defaultOptions, options)),
		options,
	);
}
